package com.zeng.context;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.naming.*;
import javax.servlet.ServletContext;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zyb
 * @date 2021-03-07 10:14
 */
public class ComponentContext {
    private static final String CONTEXT_NAME = ComponentContext.class.getName();

    private static final String COMPONENT_ENV_CONTEXT_NAME = "java:comp/env";

    private static ServletContext servletContext;

    private Context envContext;

    private final Map<String, Object> beanCacheMap = new HashMap<>();


    public void init(ServletContext servletContext) {
        servletContext.setAttribute(CONTEXT_NAME, this);
        ComponentContext.servletContext = servletContext;

        initEntContext();
        listAllComponent();
        populateBean();
        initializeBean();
    }


    private void listAllComponent() {
        listComponent("", envContext);
    }

    private void listComponent(String parentBeanPath, Context context) {
        try {
            NamingEnumeration<Binding> namingEnumeration = context.listBindings("/");
            while (namingEnumeration.hasMore()) {
                Binding binding = namingEnumeration.next();
                String name = binding.getName();
                Object bindingObject = binding.getObject();
                String beanPathName = parentBeanPath + "/" + name;
                if (!(bindingObject instanceof Context)) {
                    beanCacheMap.put(beanPathName.substring(1), bindingObject);
                    continue;
                }
                Context childrenContext = (Context) bindingObject;
                listComponent(beanPathName, childrenContext);
            }
        } catch (NamingException e) {
            throw new RuntimeException(e);
        }
    }

    private void populateBean() {
        Collection<Object> beans = beanCacheMap.values();
        beans.forEach(bean -> {
            Class<?> beanClass = bean.getClass();
            Arrays.stream(beanClass.getDeclaredFields())
                    .filter(field -> !Modifier.isStatic(field.getModifiers()))
                    .filter(field -> field.isAnnotationPresent(Resource.class))
                    .forEach(field -> {
                        Resource resource = field.getAnnotation(Resource.class);
                        Object lookupBean = beanCacheMap.get(resource.name());
                        field.setAccessible(true);
                        try {
                            field.set(bean, lookupBean);
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }
                    });
        });
    }

    /**
     * 初始化带有{@link PostConstruct}的方法
     */
    private void initializeBean() {
        Collection<Object> beans = beanCacheMap.values();
        beans.forEach(bean -> invokeMethodAnnotation(bean, PostConstruct.class));
    }

    private void invokeMethodAnnotation(Object bean, Class<? extends Annotation> annotationClass) {
        Class<?> beanClass = bean.getClass();
        Arrays.stream(beanClass.getDeclaredMethods())
                .filter(method -> !Modifier.isStatic(method.getModifiers()))
                .filter(method -> method.getParameterCount() == 0)
                .filter(method -> method.isAnnotationPresent(annotationClass))
                .forEach(method -> {
                    try {
                        method.invoke(bean);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        throw new RuntimeException(e);
                    }
                });
    }

    /**
     * 初始化{@link Context}
     */
    private void initEntContext() {

        try {
            Context initCtx = new InitialContext();
            this.envContext = (Context) initCtx.lookup(COMPONENT_ENV_CONTEXT_NAME);
        } catch (NamingException e) {
            throw new RuntimeException("初始化 context失败.", e);
        }
    }

    public static ComponentContext getInstance() {
        return (ComponentContext) servletContext.getAttribute(CONTEXT_NAME);

    }

    private static Context getContext() {
        return getInstance().envContext;
    }

    /**
     * 依赖查找
     *
     * @param name
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T lookup(String name) {
        T t = (T) getInstance().beanCacheMap.get(name);
        if (t == null) {
            throw new RuntimeException("查找bean:" + name + "失败");

        }
        return t;
    }

    /**
     * 初始化带有{@link PreDestroy}的方法
     */
    private void destroyBean() {
        Collection<Object> beans = beanCacheMap.values();
        beans.forEach(bean -> invokeMethodAnnotation(bean, PreDestroy.class));
    }

    public void destroy() {
        destroyBean();
    }
}
